/*
 * Funambol is a mobile platform developed by Funambol, Inc. 
 * Copyright (C) 2003 - 2007 Funambol, Inc.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission 
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE 
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Affero General Public License 
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 * 
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite 
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 * 
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 * 
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably 
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */
#include "RecurrenceDataConverter.h"
#include "base/util/StringBuffer.h"
#include "vocl/VProperty.h"
#include "vocl/VObject.h"
#include "vocl/VConverter.h"
#include "Recurrence.h"
#include "base/Log.h"

/**
 * daysOfWeekMask -> string.
 * @return  a string with days of week formatted (like "SU MO TU FR"),
 *          based on the daysOfWeek mask passed.
 * @note    returns a new allocated string, must be freed by the caller.
 */
char* daysOfWeekToString(int l) {
    
    if (l<0 || l>128)
        return NULL;
    
    //SU MO TU WE TH FR SA
    char* ret = new char[22];
    strcpy(ret, "");
    
    if(l & 1)    strcat(ret, "SU ");
    if(l & 2)    strcat(ret, "MO ");
    if(l & 4)    strcat(ret, "TU ");
    if(l & 8)    strcat(ret, "WE ");
    if(l & 16)   strcat(ret, "TH ");
    if(l & 32)   strcat(ret, "FR ");
    if(l & 64)   strcat(ret, "SA ");
    
    return ret;
}

/**
 * string -> dayOfWeekMask
 * Calculate the dayOfWeekMask based on the input string
 * of days (like "SU MO TU FR").
 */
int stringToDaysOfWeek(char* in) {
    int ret = 0;
    
    char* index;
    index = NULL;
    index = strstr(in, "SU");
    if(index)
        ret += 1;
    
    index = NULL;
    index = strstr(in, "MO");
    if(index)
        ret += 2;
    
    index = NULL;
    index = strstr(in, "TU");
    if(index)
        ret += 4;
    
    index = NULL;
    index = strstr(in, "WE");
    if(index)
        ret += 8;
    
    index = NULL;
    index = strstr(in, "TH");
    if(index)
        ret += 16;
    
    index = NULL;
    index = strstr(in, "FR");
    if(index)
        ret += 32;
    
    index = NULL;
    index = strstr(in, "SA");
    if(index)
        ret += 64;
    
    return ret;
}

/**
 * Returns true if input string is a day of week.
 */
bool isWeekDay(char* data) {
    
    bool ret = false;
    const char* weekDay[] = {"SU", "MO", "TU", "WE", "TH", "FR", "SA"};
    
    for(int i=0; i<7 ; i++) {
        if(!strcmp(data, weekDay[i]))
            return true;
    }
    return ret;
}


char* RecurrenceDataConverter::getVObjectPropertyValue(VObject* vo, const char* propertyName) {

    char* propertyValue = NULL;
    VProperty* vprop = vo->getProperty(propertyName);
    if (vprop && vprop->getValue()) {
        propertyValue = vprop->getValue();
    }
    return propertyValue;
}


const char* RecurrenceDataConverter::format(Recurrence& recurrence) {
    
    rrule = "";
    
    char* days = NULL;
    char value[128];
    strcpy(value, "");
    
    Frequency  freq     = daily;
    int  interval       = 1;
    int  occurrences    = 0;
    int  dayofweek      = 0;
    int  dayofmonth     = 0;
    // int  instance       = 1;
    int  monthofyear    = 0;
    bool noEnd          = false;
    
    freq            = recurrence.getFrequency();
    occurrences     = recurrence.getOccurrences();
    interval        = recurrence.getInterval();
    dayofweek       = recurrence.getDayOfWeek();
    noEnd           = recurrence.getNoEndDate();
    dayofmonth      = recurrence.getDayOfMonth();
    monthofyear     = recurrence.getMonthOfYear();
    
    switch(freq) {
            
        // Daily = 1
        case daily: {
            if(interval > 0)
                sprintf(value, "D%ld #%ld", interval, occurrences);
            else if (dayofweek > 0) {
                days = daysOfWeekToString(dayofweek);
                if (days) {
                    sprintf(value, "W1 %s #%ld", days, occurrences);
                    delete [] days; days = NULL;
                }
            }
            break;
        }
        // Weekly = 2
        case weekly: {
            if (dayofweek > 0) {
                days = daysOfWeekToString(dayofweek);
                if (days) {
                    if(noEnd) {
                        sprintf(value, "W%ld %s #0", interval, days);
                    } else {
                        sprintf(value, "W%ld %s #%ld", interval, days, occurrences);
                    }
                    delete [] days;
                }
            }
            break;
        }
        // Monthly = 3
        case monthly: {
            if(dayofmonth > 0) {
                if(noEnd)
                    sprintf(value, "MD%ld %ld #0", interval, dayofmonth);
                else {
                    sprintf(value, "MD%ld %ld #%ld", interval, dayofmonth, occurrences);
                }
                break;
            }
        }    
        // yearly = 4
        case yearly: {
            if(dayofmonth>0 && monthofyear>0) {
                if(noEnd)
                    sprintf(value, "YM1 %ld #0", monthofyear);
                else
                    sprintf(value, "YM1 %ld #%ld", monthofyear, occurrences);
            }
            break;
        }
        default: {
            LOG.debug("Property not supported in formatting the rrule...");
        }
    }
     
    rrule = value;
    return rrule;
    
}

Recurrence* RecurrenceDataConverter::parse(const char* stringEvent, Date& startDate) {
    
    // this is the timezone of the date that is used
    Timezone& tz = (Timezone&)startDate.getTimezone();
    
    int ret = 0;
    Recurrence* recurrence = NULL;
    
    char* str = strdup(stringEvent);
        
    char seps[] = TEXT(" ");
    char* token = strtok(str, seps);
    
    Frequency freq = frequency_empty;
    int interval    =  0;
    int occurences  =  0;
    int dayOfMonth  = -1;
    // int weekOfMonth = -1;    // used for monthlyTh
    int monthOfYear = -1;
	char* days = new char[30];
	char* mOfYear = new char[20];
	//To break the if-loops we need a variable to check on
    Boolean gotoerror = false;
	Boolean gotofinally = false;
	
    if (!token) {
        gotoerror = true;
    }
    
    //
    // First token will be: "D|W|MP|MD|YM|YD<interval>"
    //
	if(!gotoerror) {
		if(token[0] == TEXT('D')) {
			freq = daily; 
			token ++;
		}
		else if(token[0] == TEXT('W')) {
			freq = weekly;
			token ++;
		}
		else if(token[0] == TEXT('M') && token[1] == TEXT('D')) {
			freq = monthly;
			token += 2;
		}
		else if(token[0] == TEXT('M') && token[1] == TEXT('P')) {
			// not yet supported 
			LOG.debug("RecurrenceDataConverter::parse. The recurrence montlyth is not yet supported. Recurrence is not considered...");
			gotofinally = true;
			// recType = winRecursMonthNth;
			// token += 2;
		}
		else if(token[0] == TEXT('Y') && token[1] == TEXT('D')) {
			// "YD" Not supported!!
			LOG.debug("RecurrenceDataConverter::parse - Warning: RecurrenceType 'YD' not supported: \"%ls\"",stringEvent);
			gotofinally = true;
		}
		else if(token[0] == TEXT('Y') && token[1] == TEXT('M')) {
			freq = yearly;
			token += 2;
		}
		
		if(!gotofinally) {
			interval = atoi(token);
			if(!interval || freq == frequency_empty) {
				gotoerror = true;
			}
			
			if(!gotoerror) {
				strcpy(days, "");
				
				strcpy(mOfYear, "");
				recurrence = new Recurrence();
				
				token = strtok(NULL, seps);
				while (token) {
					//
					// Daily = 1
					//
					if(freq == daily) {
						if(strchr(token, '#')) {
							recurrence->setFrequency(freq);
							recurrence->setInterval(interval);
							occurences = atoi(token+1);
							if(occurences == 0) {
								recurrence->setNoEndDate(true);
							}
							else {
								recurrence->setNoEndDate(false);
								recurrence->setOccurrences(occurences);
							}
							token ++;
						}
						else if(token[8] == TEXT('T')) {
							recurrence->setFrequency(freq);
							recurrence->setInterval(interval);
							Date d(token, tz);
							recurrence->setPatternEndDate(d);
						}
					}
					//
					// Weekly = 2
					//
					else if(freq == weekly) {
						if(strchr(token, '#')) {
							recurrence->setFrequency(freq);
							recurrence->setInterval(interval);
							occurences = atoi(token+1);
							if(occurences == 0) {
								recurrence->setNoEndDate(true);
							}
							else {
								recurrence->setOccurrences(occurences);
							}
							
							if(strcmp(days, "")) {     
								recurrence->setDayOfWeek(stringToDaysOfWeek(days));
							}
							else {
								recurrence->setDayOfWeek(getDayOfWeekFromStandardDate(startDate));
							}
							token++;
						}
						else if(token[8] == TEXT('T')) {
							recurrence->setFrequency(freq);
							Date d(token, tz);
							recurrence->setPatternEndDate(d);
							recurrence->setInterval(interval);
							if(strcmp(days, "")) {
								recurrence->setDayOfWeek(stringToDaysOfWeek(days));
							}
							else  {
								recurrence->setDayOfWeek(getDayOfWeekFromStandardDate(startDate));
							}
						}
						else if(isWeekDay(token)) {
							strcat(days, token);
							strcat(days, " ");
						}
					}
					//
					// Monthly = 3
					//
					else if (freq == monthly) {
						// VCalendar supports as Monthly by day rules: MD<interval> listofdates|listofdates(from end of month) <end tag>
						// On Outlook recurrence pattern only MD<interval> day <end tag> can be represented
						if(strchr(token, '#')) {
							recurrence->setFrequency(freq);
							recurrence->setDayOfMonth(dayOfMonth);
							
							recurrence->setInterval(interval);
							occurences = atoi(token+1);
							if(occurences == 0) {
								recurrence->setNoEndDate(true);
							}
							else {
								recurrence->setOccurrences(occurences);
							}
							token++;
						}
						else if(token[8] == 'T') {
							recurrence->setFrequency(freq);
							Date d(token, tz);
							recurrence->setPatternEndDate(d);
							recurrence->setInterval(interval);
							recurrence->setDayOfMonth(dayOfMonth);
						}
						else {
							if(dayOfMonth != -1) {
								gotoerror = true;
								break;
							}
							dayOfMonth = atoi(token);
							if(dayOfMonth == 0) {
								gotoerror = true;
								break;
							}
						}
					}
					//
					// MonthNth = 5
					//
					
					else if(freq == monthlyTh) {
						LOG.info("Recurrence monthlyth: not supported yet...");
						/*
						if(wcschr(token, TEXT('#'))) {
							setIntProperty(L"RecurrenceType", recType);
							setIntProperty(L"Interval",      interval);
							setIntProperty(L"Instance",   weekOfMonth);
							setIntProperty(L"DayOfWeekMask", stringToDaysOfWeek(days));
							occurences = _wtoi(token+1);
							if(occurences == 0)             setIntProperty(L"NoEndDate",   1);
							else                            setIntProperty(L"Occurrences", occurences);
							token++;
						}
						else if(token[8] == TEXT('T')) {
							setIntProperty(L"RecurrenceType", recType);
							setProperty(L"PatternEndDate",      token);
							setIntProperty(L"Interval",      interval);
							setIntProperty(L"Instance",   weekOfMonth);
							setIntProperty(L"DayOfWeekMask", stringToDaysOfWeek(days));
						}
						else if(isWeekDay(token)) {
							wcscat(days, token);
							wcscat(days, TEXT(" "));
						}
						else {
							if(token[1] != TEXT('+') && token[1] != TEXT('-')) {
								goto error;
							}
							WCHAR sWeek[] = TEXT("\0\0");
							sWeek[0] = token[0];
							weekOfMonth = _wtoi(sWeek);
							if(token[1] == TEXT('-')) {
								// it's the # of weeks to the end of month...
								weekOfMonth = 5 - weekOfMonth;
							}
							if(weekOfMonth > 5 || weekOfMonth < 1) {
								goto error;
							}
						}
						 */
					}
					//
					// Yearly = 4
					//
					else if(freq == yearly) {
						// Expected sequence will be YM1 month <end tag>
						if(strchr(token, '#')) {
							recurrence->setFrequency(freq);
							recurrence->setInterval(interval);
							recurrence->setMonthOfYear(monthOfYear);
							recurrence->setDayOfMonth(dayOfMonth);
							
							occurences = atoi(token+1);
							if(occurences == 0) {
								recurrence->setNoEndDate(true);
							}
							else {
								recurrence->setOccurrences(occurences);
							}
							token++;
						}
						else if(token[8] == 'T') {
							recurrence->setFrequency(freq);
							Date d(token, tz);
							recurrence->setPatternEndDate(d);

							recurrence->setInterval(interval);
							recurrence->setMonthOfYear(monthOfYear);
							recurrence->setDayOfMonth(dayOfMonth);
							
						}
						else {
							if(strcmp(mOfYear, "")) {
								gotoerror = true;
								break;
							}
							strcat(mOfYear, token);
							monthOfYear = atoi(mOfYear);
							
							recurrence->setFrequency(freq);
							Date d(startDate);
							dayOfMonth = getDayOfMonthFromStandardDate(d);
							
							recurrence->setInterval(interval);
							recurrence->setMonthOfYear(monthOfYear);
							recurrence->setDayOfMonth(dayOfMonth);
						}
					}
					token = strtok(NULL, seps);
				}
				
				ret = 0;
				gotofinally = true;
			}
		}
	}
    
	if(gotoerror) {
		LOG.error("RecurrenceDataConverter::parse error, bad RRULE format: %ls", stringEvent);
		gotofinally = true;
	}
    
	if(gotofinally) {
		if (str)     delete [] str;
		if (days)    delete [] days;
		if (mOfYear) delete [] mOfYear;
		return recurrence;
	}
} 

